package driver;

import java.util.ArrayDeque;
import java.util.ArrayList;

import state.Globals;

import driver.actions.BaseAction;

public class ActionDriver implements Runnable {
    
    public static int MAX_THREADS = 20;
    private static ActionDriver driver;
    private static Thread runThread;
    
    public static void init()
    {
        driver = new ActionDriver();
        runThread = new Thread(driver);
        runThread.start();
    }
    
    public static ActionDriver getInstance()
    {
        return driver;
    }
    
    public static void shutdown()
    {
        driver.shouldContinue = false;
        
        synchronized (driver.actionQueue)
        {
            driver.actionQueue.notify();
        }
        
        try
        {
            runThread.join();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
    
    private boolean shouldContinue = true;
    
    private ArrayDeque<BaseAction> actionQueue = new ArrayDeque<BaseAction>();
    private ArrayList<ActionHandlerThread> handlerThreads = new ArrayList<ActionHandlerThread>();
    private ActionDelayQueue delayQueue = new ActionDelayQueue();
    
    private ActionDriver()
    {
        delayQueue.start();
    }
    
    public void executeAction(long delay, BaseAction action)
    {
        if (Globals.isDebug())
        {
            Globals.getLogger().log("Adding action to queue: " + action);
        }
        
        if (delay > 0)
        {
            delayQueue.appendAction(delay, action);
        }
        else
        {
            synchronized (actionQueue)
            {
                actionQueue.push(action);
                
                actionQueue.notify();
            }
        }
    }
    
    public void notifyCompletion()
    {
        synchronized (handlerThreads)
        {
            handlerThreads.notify();
        }
    }
    
    public void run()
    {
        runThread.setName("Action Driver");
        while (shouldContinue)
        {
            BaseAction action = null;
            synchronized (actionQueue)
            {
                if (actionQueue.isEmpty())
                {
                    try
                    {
                        actionQueue.wait();
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
                
                if (!shouldContinue)
                {
                    break;
                }
                
                action = actionQueue.pop();
            }
            
            boolean foundHandler = false;
            
            while (!foundHandler && shouldContinue)
            {
                for (ActionHandlerThread t : handlerThreads)
                {
                    if (t.setWorkItem(action))
                    {
                        if (Globals.isDebug())
                        {
                            Globals.getLogger().log("Assigned action " + action + " to Action Handler: " + t.getThread());
                        }
                        foundHandler = true;
                        break;
                    }
                }

                if (foundHandler)
                {
                    
                    
                    break;
                }
                
                synchronized (handlerThreads)
                {
                
                    if (handlerThreads.size() < MAX_THREADS)
                    {
                        ActionHandlerThread t = new ActionHandlerThread();
                        
                        t.start();
                        handlerThreads.add(t);
                    }
                    
                    try
                    {
                        handlerThreads.wait();
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
            
        }
        
        delayQueue.shutdown();
        
        for (ActionHandlerThread t : handlerThreads)
        {
            t.shutdown();
        }
    }
    
}
